package com.tonyodev.fetch2.downloader;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventPublishInfo;
import ohos.rpc.RemoteException;

import com.tonyodev.fetch2.Download;
import com.tonyodev.fetch2.FetchIntent;
import com.tonyodev.fetch2.exception.FetchException;
import com.tonyodev.fetch2.fetch.ListenerCoordinator;
import com.tonyodev.fetch2.helper.DownloadInfoUpdater;
import com.tonyodev.fetch2.helper.FileDownloaderDelegate;
import com.tonyodev.fetch2.provider.GroupInfoProvider;
import com.tonyodev.fetch2.provider.NetworkInfoProvider;
import com.tonyodev.fetch2.util.FetchUtils;
import com.tonyodev.fetch2core.Downloader;
import com.tonyodev.fetch2core.FetchCoreUtils;
import com.tonyodev.fetch2core.FileServerDownloader;
import com.tonyodev.fetch2core.Intrinsics;
import com.tonyodev.fetch2core.Logger;
import com.tonyodev.fetch2core.StorageResolver;

import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @since 2021-05-20
 */
public class DownloadManagerImpl implements DownloadManager {
    private final Object lock = new Object();
    private ExecutorService executor;
    private volatile int concurrentLimit;
    private HashMap<Integer, FileDownloader> currentDownloadsMap;
    private volatile int downloadCounter;
    private volatile boolean closed;
    private Downloader<?, ?> httpDownloader;
    private long progressReportingIntervalMillis;
    private Logger logger;
    private NetworkInfoProvider networkInfoProvider;
    private boolean retryOnNetworkGain;
    private DownloadInfoUpdater downloadInfoUpdater;
    private DownloadManagerCoordinator downloadManagerCoordinator;
    private ListenerCoordinator listenerCoordinator;
    private FileServerDownloader fileServerDownloader;
    private boolean hashCheckingEnabled;
    private StorageResolver storageResolver;
    private Context context;
    private String namespace;
    private GroupInfoProvider groupInfoProvider;
    private int globalAutoRetryMaxAttempts;
    private boolean preAllocateFileOnCreation;

    public DownloadManagerImpl(@NotNull Downloader httpDownloader, int concurrentLimit,
                               long progressReportingIntervalMillis, @NotNull Logger logger,
                               @NotNull NetworkInfoProvider networkInfoProvider, boolean retryOnNetworkGain,
                               @NotNull DownloadInfoUpdater downloadInfoUpdater,
                               @NotNull DownloadManagerCoordinator downloadManagerCoordinator,
                               @NotNull ListenerCoordinator listenerCoordinator,
                               @NotNull FileServerDownloader fileServerDownloader, boolean hashCheckingEnabled,
                               @NotNull StorageResolver storageResolver, @NotNull Context context,
                               @NotNull String namespace, @NotNull GroupInfoProvider groupInfoProvider,
                               int globalAutoRetryMaxAttempts, boolean preAllocateFileOnCreation) {
        this.httpDownloader = httpDownloader;
        this.progressReportingIntervalMillis = progressReportingIntervalMillis;
        this.logger = logger;
        this.networkInfoProvider = networkInfoProvider;
        this.retryOnNetworkGain = retryOnNetworkGain;
        this.downloadInfoUpdater = downloadInfoUpdater;
        this.downloadManagerCoordinator = downloadManagerCoordinator;
        this.listenerCoordinator = listenerCoordinator;
        this.fileServerDownloader = fileServerDownloader;
        this.hashCheckingEnabled = hashCheckingEnabled;
        this.storageResolver = storageResolver;
        this.context = context;
        this.namespace = namespace;
        this.groupInfoProvider = groupInfoProvider;
        this.globalAutoRetryMaxAttempts = globalAutoRetryMaxAttempts;
        this.preAllocateFileOnCreation = preAllocateFileOnCreation;
        this.executor = this.getNewDownloadExecutorService(concurrentLimit);
        this.concurrentLimit = concurrentLimit;
        boolean var19 = false;
        this.currentDownloadsMap = new HashMap<>();
    }

    @Override
    public boolean getIsClosed() {
        return closed;
    }

    @Override
    public int getConcurrentLimit() {
        return concurrentLimit;
    }

    @Override
    public void setConcurrentLimit(int concurrentLimit) {
        this.concurrentLimit = concurrentLimit;
    }

    @Override
    public boolean start(Download download) {
        Intrinsics.checkParameterIsNotNull(download, "download");
        if (currentDownloadsMap.containsKey(download.getId())) {
            logger.d("DownloadManager already running download " + download);
            return false;
        }
        if (downloadCounter >= concurrentLimit) {
            logger.d("DownloadManager cannot init download " + download + " because " +
                "the download queue is full");
            return false;
        }
        downloadCounter += 1;
        currentDownloadsMap.put(download.getId(), null);
        downloadManagerCoordinator.addFileDownloader(download.getId(), null);

        ExecutorService downloadExecutor = executor;
        if (downloadExecutor != null && !downloadExecutor.isShutdown()) {
            downloadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.currentThread().setName(download.getNamespace() + "-" + download.getId());
                    } catch (Exception e) {

                    }
                    try {
                        FileDownloader fileDownloader = getNewFileDownloaderForDownload(download);
                        boolean runDownload = false;
                        synchronized (lock) {
                            if (currentDownloadsMap.containsKey(download.getId())) {
                                fileDownloader.setDelegate(getFileDownloaderDelegate());
                                currentDownloadsMap.put(download.getId(), fileDownloader);
                                downloadManagerCoordinator.addFileDownloader(download.getId(), fileDownloader);
                                logger.d("DownloadManager starting download " + download);
                                runDownload = true;
                            } else {
                                runDownload = false;
                            }
                        }

                        if (runDownload) {
                            fileDownloader.run();
                        }
                        removeDownloadMappings(download);
                        groupInfoProvider.clean();
                    } catch (Exception e) {
                        logger.e("DownloadManager failed to start download " + download, e);
                        StackTraceElement[] stackTrace = e.getStackTrace();
                        for (StackTraceElement stackTraceElement : stackTrace) {
                            System.out.println(stackTraceElement.getClassName() + " : " + stackTraceElement.getMethodName());
                        }
                        System.out.println("error ljc " + e.getMessage());
                    } finally {
                        removeDownloadMappings(download);

                        Intent intent = new Intent();
                        Operation operation = new Intent.OperationBuilder()
                            .withAction(FetchIntent.ACTION_QUEUE_BACKOFF_RESET)
                            .build();
                        intent.setOperation(operation);
                        intent.setParam(FetchIntent.EXTRA_NAMESPACE, namespace);
                        CommonEventData eventData = new CommonEventData(intent);
                        CommonEventPublishInfo publishInfo = new CommonEventPublishInfo();
                        String[] permissions = {"com.tonyodev.fetch2.permission.PUBLISHINFO"};
                        publishInfo.setSubscriberPermissions(permissions); // 设置权限
                        try {
                            CommonEventManager.publishCommonEvent(eventData, publishInfo);  // 发布公共事件。
                        } catch (RemoteException e) {
                            logger.d("Exception occurred during publishCommonEvent invocation.");
                        }
                    }
                }
            });
            return true;
        } else {
            return false;
        }
    }

    private void removeDownloadMappings(Download download) {
        synchronized (lock) {
            if (currentDownloadsMap.containsKey(download.getId())) {
                currentDownloadsMap.remove(download.getId());
                downloadCounter -= 1;
            }
            downloadManagerCoordinator.removeFileDownloader(download.getId());
        }
    }

    @Override
    public boolean cancel(int downloadId) {
        synchronized (lock) {
            return cancelDownloadNoLock(downloadId);
        }
    }

    private boolean cancelDownloadNoLock(int downloadId) {
        throwExceptionIfClosed();
        FileDownloader fileDownloader = currentDownloadsMap.get(downloadId);
        if (fileDownloader != null) {
            fileDownloader.setInterrupted(true);
            currentDownloadsMap.remove(downloadId);
            downloadCounter -= 1;
            downloadManagerCoordinator.removeFileDownloader(downloadId);
            logger.d("DownloadManager cancelled download" + fileDownloader.getDownload());
            return fileDownloader.getInterrupted();
        } else {
            downloadManagerCoordinator.interruptDownload(downloadId);
            return false;
        }
    }

    @Override
    public void cancelAll() {
        synchronized (lock) {
            throwExceptionIfClosed();
            cancelAllDownloads();
        }
    }

    private void cancelAllDownloads() {
        if (concurrentLimit > 0) {
            List<FileDownloader> lists = downloadManagerCoordinator.getFileDownloaderList();
            if (null != lists && lists.size() > 0) {
                for (FileDownloader fileDownloader : lists) {
                    if (null != fileDownloader) {
                        fileDownloader.setInterrupted(true);
                        downloadManagerCoordinator.removeFileDownloader(fileDownloader.getDownload().getId());
                        logger.d("DownloadManager cancelled download " + fileDownloader.getDownload());
                    }

                }
            }
        }
        currentDownloadsMap.clear();
        downloadCounter = 0;
    }

    @Override
    public boolean contains(int downloadId) {
        synchronized (lock) {
            return !getIsClosed() && downloadManagerCoordinator.containsFileDownloader(downloadId);
        }
    }

    @Override
    public boolean canAccommodateNewDownload() {
        synchronized (lock) {
            return !closed && downloadCounter < concurrentLimit;
        }
    }

    @Override
    public int getActiveDownloadCount() {
        synchronized (lock) {
            throwExceptionIfClosed();
            return downloadCounter;
        }
    }

    @Override
    public List<Download> getActiveDownloads() {
        List<Download> list = new ArrayList<>();
        synchronized (lock) {
            throwExceptionIfClosed();
            Set<Integer> set = currentDownloadsMap.keySet();
            if (null != set) {
                for (int i : set) {
                    if (null != currentDownloadsMap.get(i)) {
                        list.add(currentDownloadsMap.get(i).getDownload());
                    }
                }
            }
            return list;
        }
    }

    @Override
    public List<Integer> getActiveDownloadsIds() {
        List<Integer> list = new ArrayList<>();
        synchronized (lock) {
            throwExceptionIfClosed();
            Set<Integer> set = currentDownloadsMap.keySet();
            if (null != set) {
                for (int i : set) {
                    if (null != currentDownloadsMap.get(i)) {
                        list.add(i);
                    }
                }
            }
        }
        return list;
    }

    @Override
    public FileDownloader getNewFileDownloaderForDownload(Download download) {
        Intrinsics.checkParameterIsNotNull(download, "download");
        FileDownloader fileDownloader;
        if (!FetchCoreUtils.isFetchFileServerUrl(download.getUrl())) {
            fileDownloader = getFileDownloader(download, this.httpDownloader);
        } else {
            fileDownloader = getFileDownloader(download, fileServerDownloader);
        }
        return fileDownloader;
    }

    @Override
    public FileDownloader.Delegate getFileDownloaderDelegate() {
        return new FileDownloaderDelegate(this.downloadInfoUpdater, this.listenerCoordinator.getMainListener(),
            this.retryOnNetworkGain, this.globalAutoRetryMaxAttempts);
    }

    @Override
    public String getDownloadFileTempDir(Download download) {
        Intrinsics.checkParameterIsNotNull(download, "download");
        Downloader.ServerRequest request = FetchUtils.getRequestForDownload(download);
        return this.storageResolver.getDirectoryForFileDownloaderTypeParallel(request);
    }


    private final void throwExceptionIfClosed() {
        if (this.closed) {
            throw new FetchException("DownloadManager is already shutdown.");
        }
    }

    private FileDownloader getFileDownloader(Download download, Downloader<?, ?> downloader) {
        Downloader.ServerRequest originalRequest = FetchUtils.getRequestForDownload(download);
        Downloader.ServerRequest request;
        FileDownloader fileDownloader;
        if (downloader.getHeadRequestMethodSupported(originalRequest)) {
            request = FetchUtils.getRequestForDownload(download, "HEAD");
        } else {
            request = originalRequest;
        }
        Set<Downloader.FileDownloaderType> supportedDownloadTypes = downloader.getRequestSupportedFileDownloaderTypes(request);
        if (downloader.getRequestFileDownloaderType(request, supportedDownloadTypes) == Downloader.FileDownloaderType.SEQUENTIAL) {
            fileDownloader = new SequentialFileDownloaderImpl(download, downloader, this.progressReportingIntervalMillis, this.logger, this.networkInfoProvider, this.retryOnNetworkGain, this.hashCheckingEnabled, this.storageResolver, this.preAllocateFileOnCreation);
        } else {
            fileDownloader = new ParallelFileDownloaderImpl(download, downloader, this.progressReportingIntervalMillis, this.logger, this.networkInfoProvider, this.retryOnNetworkGain, this.storageResolver.getDirectoryForFileDownloaderTypeParallel(request), this.hashCheckingEnabled, this.storageResolver, this.preAllocateFileOnCreation);
        }
        return fileDownloader;
    }


    private final ExecutorService getNewDownloadExecutorService(int concurrentLimit) {
        return concurrentLimit > 0 ? Executors.newFixedThreadPool(concurrentLimit) : null;
    }

    @Override
    public void close() throws IOException {
        synchronized (lock) {
            if (closed) {
                return;
            }
            closed = true;
            if (concurrentLimit > 0) {
                terminateAllDownloads();
            }
            logger.d("DownloadManager closing download manager");
            try {
                if (null != executor) {
                    executor.shutdown();
                }
            } catch (Exception e) {
            }
        }
    }

    private void terminateAllDownloads() {
        List<FileDownloader> lists = new ArrayList<>();
        if (null != currentDownloadsMap && currentDownloadsMap.size() > 0) {
            for (FileDownloader fileDownloader : currentDownloadsMap.values()) {
                if (null != fileDownloader) {
                    fileDownloader.setTerminated(true);
                    downloadManagerCoordinator.removeFileDownloader(fileDownloader.getDownload().getId());
                    logger.d("DownloadManager terminated download " + fileDownloader.getDownload());
                }
            }
        }
        currentDownloadsMap.clear();
        downloadCounter = 0;
    }
}
